"""Secure Remote Passwords.  This is slightly different from the standard
implementation (with regard to the definition of 'u', the authentication
hash, and the fact that the database is a pickle).  Also the default random
number generator is not cryptographically strong.  It may be good enough to
password protect your MUD, if not your bank account.  Note that the passwd
database should not be world readable, or it will be vulnerable to a
dictionary attack (like the standard Unix password file).  See the SRP
distribution at http://srp.stanford.edu/srp/ for more information."""

# this code was originally fetched from
# <http://members.tripod.com/professor_tom/archives/srpsocket.html>
# and modified for my needs

import sha
import random

# Some constants defining the sizes of various entities.

saltlen = 32    # bytes
tlen = 128      # bits
k = 3
ablen = 19 * 8  # bits
# don't know why exactly 19*8 but in mangos it is so..

# The prime field to work in, and the base to use.  Note that this must be
# common to both client and host. (Alternatively, the host can send these
# values to the client, who should then verify that they are safe.)
# The first number is a prime p of the form 2q + 1, where q is also prime.
# The second number is a generator in the field GF(p).

n = 62100066509156017342069496140902949863249758336000796928566441170293728648119L
g = 7L

# New exceptions we raise.

class NoSuchUser(Exception): pass
class ImproperKeyValue(Exception): pass
class AuthFailure(Exception): pass

# Some utility functions:

def random_long(bits):

    """Generate a random long integer with the given number of bits."""

    r = 0L
    chunk = 24
    bchunk = (1 << chunk) - 1
    while bits > 0:
	if bits < chunk:
	    bchunk = (1 << bits) - 1
	i = random.randint(0, bchunk)
	r = (r << chunk) + i
	bits = bits - chunk
    return r

def random_string(bytes):

    """Generate a random string with the given number of bytes."""

    r = ''
    for i in range(0, bytes):
	r = r + chr(random.randint(0, 255))
    return r

def string_to_long(s):

    """Convert a string of bytes into a long integer."""

    r = 0L
    for c in s:
	r = (r << 8) + ord(c)
    return r

def long_to_string(i):

    """Convert a long integer into a string of bytes."""

    s = ''
    while i > 0:
	s = chr(i & 255) + s
	i = i >> 8
    return s

def hash(s):

    """Hash a value with some hashing algorithm."""

    if type(s) != type(''):
	s = long_to_string(s)

    return sha.new(s).digest()

def private_key(u, s, p):

    """Given the username, salt, and cleartext password, return the private
    key, which is the long integer form of the hashed arguments."""

    h = hash(s[::-1] + hash(u + ':' + p))
    x = string_to_long(h[::-1])
    return x

# This creates a new entry for the host password database.  In other words,
# this is called when the user changes his password.
# Note that when this is done over the network, the channel should be
# encrypted.  The password should obviously never be sent in the clear, and
# neither should the salt, verifier pair, as they are vulnerable to a
# dictionary attack.  For the same reason, the passwd database should not be
# world readable.

def create_new_verifier(u, p):

    """Given a username, cleartext password, and a prime field, pick a
    random salt and calculate the verifier.  The salt, verifier tuple is
    returned."""

    s = random_string(saltlen)
    v = pow(g, private_key(u, s, p), n)
    return (s, v)

# This is the authentication protocol.  There are two parts, the client and
# the host.  These functions are called from the client side.

def calc_K_M(S, s, user, A, B):

 """this function calculates K & M
 may be used in both client and server
 """

 t1 = long_to_string(S)[::-2]
 t2 = long_to_string(S)[:-1][::-2]
 h1 = hash(t1)
 h2 = hash(t2)
 vK = ['x'] * 40
 for i in range(0, 20):
  vK[2*i]   = h1[i]
  vK[2*i+1] = h2[i]
 K = string_to_long(''.join(vK)[::-1])
 hash1 = hash(long_to_string(n)[::-1])
 hash2 = hash(long_to_string(g)[::-1])
 hash3 = ''.join([chr(ord(hash1[i])^ord(hash2[i])) for i in range(0, 20)])
 t3 = hash3[::-1]
 t4 = hash(user)[::-1]
 t5 = (long_to_string(K)+long_to_string(B)+long_to_string(A)+s+t4+t3)[::-1]
 M = hash(t5)[::-1]
 return K, M

def client_begin(user):

    # Pick a random number and send it to the host, who responds with
    # the user's salt and more random numbers.  Note that in the standard
    # SRP implementation, u is derived from B.

    a = random_long(ablen)
    A = pow(g, a, n)

    return (A, a)

def client_key(user, passphrase, s, B, A, a):

    u = string_to_long(hash((long_to_string(B)+long_to_string(A))[::-1])[::-1]) # random?

    if B <= 0 or n <= B:
        raise ImproperKeyValue

    # Calculate the shared, secret session key.

    x = private_key(user, s, passphrase)
    v = pow(g, x, n)
    t = B
    while t < k * v:
        t = t + n
    S = pow(t - k * v, a + u * x, n)

    K, m = calc_K_M(S, s, user, A, B)

    return (K, m)

# The next function is called from the host side.

def server_begin(v):
    
    """ some server connection initialization, generate b, B"""

    while 1:
        b = random_long(ablen)
        B = (v * k + pow(g, b, n)) % n
        if B != 0: break
    return (b, B)

def server_key(user, A, b, B, s, v):

    u = string_to_long(hash((long_to_string(B)+long_to_string(A))[::-1])[::-1])  # random?

    # We don't trust the client, who might be trying to send bogus data in
    # order to break the protocol.

    if A <= 0 or n <= A:
	raise ImproperKeyValue

    # Calculate the (private, shared secret) session key.

    t = A * pow(v, u, n) % n
    if t <= 1 or t + 1 == n:
        raise ImproperKeyValue  # WeakKeyValue -- could be our fault so retry

    S = pow(t, b, n)

    K, M = calc_K_M(S, s, user, A, B)

    return (K, M)

def host_proof(K, A, m):
 """calculates host's proof"""
 return hash((long_to_string(K)+m+long_to_string(A))[::-1])

